In [10]:
Last Updated: 2024-02-25
Cell In[10], line 1 Last Updated: 2024-02-25 ^ SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers
Notes on trading earnings releases with calendar spreads [in progress...]¶
Clone from:
Earnings releases for US listed companies are quarterly. Currently, I pick dates here from NASDAQ.
An intuitive strategy is to buy a long straddle prior to the announcement, therefore longing the volatility speculating on a large swing in either direction and loose if volatility remained small. Here, I analyze a calendar spread, concretely shorting near term volatility and longing far tenor. The shorted, elevated near implied volatility pay for the long far hedges.
In [1]:
import sys
sys.path.append('..')
from connector.quantconnect.volatility.earnings_release import *
I usually code in PyCharm, not Jupyter. That's why rather just importing much here, detailed code is in the repo.
Config¶
In [2]:
sym = 'MRNA'
equity = Equity(sym)
resolution = Resolution.second
seq_ret_threshold = 0.005
rate = DiscountRateMarket
dividend = dividend_yield = DividendYield[sym.upper()]
net_yield = rate - dividend_yield
marker_size = 4
# release_date = EarningsPreSessionDates(sym)[0]
release_date = EarningsPreSessionDates(sym)[-1]
ETL¶
The raw data originates from snapping equity and option quotes every time the underlying's price series has moved by >= 0.5%.
In [3]:
option_frame = OptionFrame.load_frame(equity, resolution, seq_ret_threshold, '1')
df = option_frame.df_options
df_equity = option_frame.df_equity
In [4]:
ts = df.index.get_level_values('ts').unique()
v_ts_pre_release = [i for i in ts if i.date() <= release_date]
v_ts_post_release = [i for i in ts if i.date() == release_date + datetime.timedelta(days=1)]
ts_pre_release = v_ts_pre_release[-1]
if v_ts_post_release:
ts_post_release = v_ts_post_release[-1]
df0 = df.loc[v_ts_pre_release]
if v_ts_post_release:
df1 = df.loc[v_ts_post_release]
expiries = list(sorted(set(df0.index.get_level_values('expiry').values)))
expiry_strike_pairs = list(sorted(set(zip(df0[df0['mid_iv'] != 0].index.get_level_values('expiry').values, df0[df0['mid_iv'] != 0].index.get_level_values('strike').values))))
expiry_strike_pairs_str = '\n'.join([f'{t[0].isoformat()} | {float(t[1])}' for t in expiry_strike_pairs])
# print(f'''Expiry | strike pairs\n: {expiry_strike_pairs_str}''')
s0 = df0.loc[ts_pre_release].iloc[0]['spot']
s1 = df1.loc[ts_post_release].iloc[0]['spot'] if v_ts_post_release else np.nan
dS_actual = s1 / s0 - 1
print(f'''Spot0: {s0}; Spot1: {s1}; dS: {round(s1-s0, 1)} | {round(100*(s1/s0-1), 1)} %''')
calcDate0 = ts_pre_release.date()
calcDate1 = calcDate0 + datetime.timedelta(days=1)
calcDates = [calcDate0, calcDate1]
exp_jump1 = sorted([i for i in expiries if i >= release_date])[0]
exp_jump2 = sorted([i for i in expiries if i >= release_date])[1]
exp_jumps = [exp_jump1, exp_jump2]
expiries_df = list(sorted(set(df.index.get_level_values('expiry').values)))
previous_non_jump_expiry = sorted([i for i in expiries_df if i < exp_jump1])[-1]
ts_pre_releaseTm7 = [i for i in ts if i.date() == previous_non_jump_expiry - datetime.timedelta(days=7)] # 7 days to avoid typical ramp of expiring options
ts_pre_releaseTm7 = ts_pre_releaseTm7[-1] if ts_pre_releaseTm7 else df.index.get_level_values('ts')[5]
dfm7 = df.loc[ts_pre_releaseTm7]
sm7 = dfm7.iloc[0]['spot']
# estimated_diffusion_iv No feasible way estimating this diffusion way for later expiries. For csco, later expiries ATM IV fell significantly even below long term IV levels.
# So now, just check plot this time atm iv over term and last release's one, then pick minimum tenor to use for hedging...
diffusion_iv = atm_iv(dfm7, previous_non_jump_expiry, sm7) # 7 days is already elevated. not jump iv!
print(f'{sym.upper()} Diffusion IV: {diffusion_iv}')
Spot0: 86.8; Spot1: nan; dS: nan | nan % MRNA Diffusion IV: 0.5237200168295723
Analysis¶
Price series¶
In [5]:
figSpot = go.Figure(go.Scatter(x=df0.index.get_level_values('ts'), y=df0['spot'], mode='lines', name='Spot'))
figSpot.update_layout(title=f'{sym} Spot', xaxis_title='Time', yaxis_title='Spot')
show(figSpot, f'{sym}_{release_date.strftime("%y%m%d")}_spot.html')